react-webcamで撮影した写真をAmazon Rekognitionで顔分析する
こんにちは、CX事業本部 IoT事業部の若槻です。
Amazon Rekognitionは、機械学習を使用した画像や動画の分析を簡単に実施できるAWSのサービスです。
今回は、Reactのカメラ撮影ライブラリreact-webcamで撮影した写真を、ローカルで起動したアプリからAmazon Rekognitionを使用して顔分析してみました。
構成
コード概要
Reactアプリケーションのコードです。
import { useRef, useState, useCallback } from "react"; import Webcam from "react-webcam"; import { makeStyles } from "@material-ui/core/styles"; import { DetectFacesRequest, DetectFacesResponse, FaceDetailList, } from "aws-sdk/clients/rekognition"; import AWS from "aws-sdk"; const useStyles = makeStyles(() => ({ webcam: { position: "absolute", top: "0px", left: "0px", visibility: "hidden", }, rekognizeResult: { flex: 1, width: "100%", flexDirection: "row", justifyContent: "flex-start", }, })); const videoConstraints = { width: 720, height: 360, facingMode: "user", }; AWS.config.update({ accessKeyId: process.env.REACT_APP_AWS_ACCESS_KEY_ID, secretAccessKey: process.env.REACT_APP_AWS_SECRET_ACCESS_KEY, region: process.env.REACT_APP_AWS_REGION, }); const rekognitionClient = new AWS.Rekognition({ apiVersion: "2016-06-27", }); //Amazon Rekognitionによる顔分析 const detectFaces = async (imageData: string): Promise<DetectFacesResponse> => { const params: DetectFacesRequest = { Image: { Bytes: Buffer.from( imageData.replace("data:image/jpeg;base64,", ""), "base64" ), }, Attributes: ["ALL"], }; return await rekognitionClient.detectFaces(params).promise(); }; //分析結果からConfidence(分析結果の信頼度)取得 const getConfidence = (rekognizeResult: DetectFacesResponse): number => { return (rekognizeResult.FaceDetails as FaceDetailList)[0].Confidence!; }; //分析結果からLowAge(推測される年齢範囲の加減)取得 const getLowAge = (rekognizeResult: DetectFacesResponse): number => { return (rekognizeResult.FaceDetails as FaceDetailList)[0].AgeRange?.Low!; }; //分析結果からHighAge(推測される年齢範囲の上限)取得 const getHighAge = (rekognizeResult: DetectFacesResponse): number => { return (rekognizeResult.FaceDetails as FaceDetailList)[0].AgeRange?.High!; }; //分析結果からEyeglasses(眼鏡を掛けているか)取得 const getIsWearingEyeGlasses = ( rekognizeResult: DetectFacesResponse ): boolean => { return (rekognizeResult.FaceDetails as FaceDetailList)[0].Eyeglasses?.Value!; }; //分析結果からEyeglasses(サングラスを掛けているか)取得 const getIsWearingSunGlasses = ( rekognizeResult: DetectFacesResponse ): boolean => { return (rekognizeResult.FaceDetails as FaceDetailList)[0].Sunglasses?.Value!; }; const App = () => { const classes = useStyles(); const [isCaptureEnable, setCaptureEnable] = useState<boolean>(false); const webcamRef = useRef<Webcam>(null); const [url, setUrl] = useState<string | null>(null); const capture = useCallback(() => { const imageSrc = webcamRef.current?.getScreenshot(); if (imageSrc) { setUrl(imageSrc); setRekognizeResult(undefined); } }, [webcamRef]); const [rekognizeResult, setRekognizeResult] = useState<DetectFacesResponse>(); const rekognizeHandler = async () => { const result: DetectFacesResponse = await detectFaces(url as string); setRekognizeResult(result); console.log(result); }; return ( <> <header> <h1>カメラアプリ(顔分析付き)</h1> </header> {isCaptureEnable || ( <button onClick={() => setCaptureEnable(true)}>開始</button> )} {isCaptureEnable && ( <> <div> <button onClick={() => setCaptureEnable(false)}>終了</button> </div> <div> <Webcam audio={false} width={540} height={360} ref={webcamRef} screenshotFormat="image/jpeg" videoConstraints={videoConstraints} className={classes.webcam} /> </div> <button onClick={capture}>キャプチャ</button> </> )} {url && ( <> <div> <button onClick={() => { setUrl(null); setRekognizeResult(undefined); }} > 削除 </button> <button onClick={() => rekognizeHandler()}>分析</button> </div> <div> <img src={url} alt="Screenshot" /> </div> {typeof rekognizeResult !== "undefined" && ( <div className={classes.rekognizeResult}> <div>{"Confidence: " + getConfidence(rekognizeResult)}</div> <div> {"AgeRange: " + getLowAge(rekognizeResult) + " ~ " + getHighAge(rekognizeResult)} </div> <div> {"Eyeglasses: " + getIsWearingEyeGlasses(rekognizeResult)} </div> <div> {"Sunglasses: " + getIsWearingSunGlasses(rekognizeResult)} </div> </div> )} </> )} </> ); }; export default App;
- react-webcamによる撮影部分は前回の記事の実装を踏襲しています。
- ローカルでの起動前提のため、アプリケーションからAWS SDKを使用して直接Amazon Rekognitionを叩き結果を取得します。
- react-webcamで取得したキャプチャデータをBase64デコードしてByteデータに変換した上で
detectFaces
に渡し、分析結果を取得しています。 - 分析結果にすべての属性を含めたい場合は、パラメータに
Attributes: ["ALL"]
を指定します。これによりAgeRange
、Eyeglasses
、Sunglasses
などの属性を取得可能となります。
IAMアクセスキーの発行
ReactアプリからのRekognitionの使用はIAMアクセスキーを用いて行います。
IAMユーザーに下記の顔検出(DetectFaces)を実施可能とするポリシーをアタッチします。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "rekognition:DetectFaces", "Resource": "*" } ] }
ユーザーのアクセスキーを発行します。
動作確認
発行したアクセスキーのクレデンシャル情報を環境変数に指定します。
$ export AWS_ACCESS_KEY_ID="<アクセスキーID>" $ export AWS_SECRET_ACCESS_KEY="<アクセスシークレットキー>" $ export AWS_REGION="<AWSリージョン>"
アプリをローカルで起動します。
$ REACT_APP_AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ REACT_APP_AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ REACT_APP_AWS_REGION=${AWS_REGION} \ npm run start
[開始]をクリックするとカメラが起動します。
[キャプチャ]をクリックします。
カメラで取得された画像が表示されます。[分析]をクリックします。
Rekognitionの分析結果が取得できました。年齢の範囲が12〜22歳とのこと。眼鏡を掛けていることをちゃんと検知できています。
裸眼。眼鏡もサングラスも掛けていないことを検知できています。
サングラスを掛けていることを検知できています。Confidenceに大きな違いはありませんが、年齢の上限が少し上がりました。
不審者スタイル。これもConfidenceに大きな違いはありませんが、年齢の下限と上限が10歳近く上がりました。顔が隠れていると子供とは判定されないようです。
detectFacesの取得結果のRawデータ(サンプル)
最後に、detectFacesの取得結果のRawデータは下記のようになります。検出した人の顔の数だけFaceDetails
配下にデータが入ります。BoundingBox
は画像内での人の顔の位置情報です。これにより検出された人の顔を画像上で矩形で囲むなど出来ます。またEmotions
により感情も取得することができます。
{ "FaceDetails": [ { "BoundingBox": { "Width": 0.20062141120433807, "Height": 0.40441733598709106, "Left": 0.15825751423835754, "Top": 0.1316724270582199 }, "AgeRange": { "Low": 21, "High": 33 }, "Smile": { "Value": true, "Confidence": 92.25345611572266 }, "Eyeglasses": { "Value": true, "Confidence": 99.9189682006836 }, "Sunglasses": { "Value": true, "Confidence": 99.63880157470703 }, "Gender": { "Value": "Female", "Confidence": 99.3447036743164 }, "Beard": { "Value": false, "Confidence": 99.50582122802734 }, "Mustache": { "Value": false, "Confidence": 99.86498260498047 }, "EyesOpen": { "Value": true, "Confidence": 99.99996948242188 }, "MouthOpen": { "Value": true, "Confidence": 99.09195709228516 }, "Emotions": [ { "Type": "HAPPY", "Confidence": 97.60749053955078 }, { "Type": "SURPRISED", "Confidence": 0.7658454179763794 }, { "Type": "CONFUSED", "Confidence": 0.5557805299758911 }, { "Type": "ANGRY", "Confidence": 0.3515094518661499 }, { "Type": "DISGUSTED", "Confidence": 0.34102049469947815 }, { "Type": "CALM", "Confidence": 0.19356855750083923 }, { "Type": "FEAR", "Confidence": 0.13799776136875153 }, { "Type": "SAD", "Confidence": 0.046782251447439194 } ], "Landmarks": [ { "Type": "eyeLeft", "X": 0.2314133197069168, "Y": 0.28564053773880005 }, { "Type": "eyeRight", "X": 0.32332608103752136, "Y": 0.2762904763221741 }, { "Type": "mouthLeft", "X": 0.24602198600769043, "Y": 0.4382118284702301 }, { "Type": "mouthRight", "X": 0.3227347433567047, "Y": 0.42984768748283386 }, { "Type": "nose", "X": 0.29952266812324524, "Y": 0.3556353449821472 }, { "Type": "leftEyeBrowLeft", "X": 0.18859225511550903, "Y": 0.2570442855358124 }, { "Type": "leftEyeBrowRight", "X": 0.2242126166820526, "Y": 0.23221562802791595 }, { "Type": "leftEyeBrowUp", "X": 0.25469353795051575, "Y": 0.23624512553215027 }, { "Type": "rightEyeBrowLeft", "X": 0.30750370025634766, "Y": 0.23125456273555756 }, { "Type": "rightEyeBrowRight", "X": 0.33047568798065186, "Y": 0.2219356745481491 }, { "Type": "rightEyeBrowUp", "X": 0.34847506880760193, "Y": 0.2408193200826645 }, { "Type": "leftEyeLeft", "X": 0.21244211494922638, "Y": 0.2877286374568939 }, { "Type": "leftEyeRight", "X": 0.24936801195144653, "Y": 0.2853952646255493 }, { "Type": "leftEyeUp", "X": 0.23162560164928436, "Y": 0.2777021527290344 }, { "Type": "leftEyeDown", "X": 0.2320021092891693, "Y": 0.2925541400909424 }, { "Type": "rightEyeLeft", "X": 0.30489200353622437, "Y": 0.27982351183891296 }, { "Type": "rightEyeRight", "X": 0.3370468020439148, "Y": 0.27489253878593445 }, { "Type": "rightEyeUp", "X": 0.3239697217941284, "Y": 0.268352746963501 }, { "Type": "rightEyeDown", "X": 0.3230580687522888, "Y": 0.28313031792640686 }, { "Type": "noseLeft", "X": 0.2710249722003937, "Y": 0.37860503792762756 }, { "Type": "noseRight", "X": 0.3051494359970093, "Y": 0.3749985694885254 }, { "Type": "mouthUp", "X": 0.29134562611579895, "Y": 0.4123360216617584 }, { "Type": "mouthDown", "X": 0.2906356453895569, "Y": 0.4593968093395233 }, { "Type": "leftPupil", "X": 0.2314133197069168, "Y": 0.28564053773880005 }, { "Type": "rightPupil", "X": 0.32332608103752136, "Y": 0.2762904763221741 }, { "Type": "upperJawlineLeft", "X": 0.14750327169895172, "Y": 0.305320143699646 }, { "Type": "midJawlineLeft", "X": 0.17698229849338531, "Y": 0.46644243597984314 }, { "Type": "chinBottom", "X": 0.28578469157218933, "Y": 0.5409565567970276 }, { "Type": "midJawlineRight", "X": 0.34007012844085693, "Y": 0.4494510591030121 }, { "Type": "upperJawlineRight", "X": 0.34829065203666687, "Y": 0.2851712107658386 } ], "Pose": { "Roll": -1.3549495935440063, "Yaw": 19.644981384277344, "Pitch": 8.039366722106934 }, "Quality": { "Brightness": 70.20365142822266, "Sharpness": 95.51618957519531 }, "Confidence": 99.99923706054688 } ] }
参考
- イメージ内の顔を検出するには - Amazon Rekognition
- /javascript
- 【React Native】これを読めば誰でも分かるFlexBoxの基本 | プログラマーになった 「中卒」 男のブログ
- Reactでちょっとだけ環境変数を使いたい
以上